// Written by octarone, licensed under GPL, see License.txt or visit <http://www.gnu.org/licenses/>

desc: Key Tuner (Master)
in_pin:none
out_pin:none
options:gmem=KeyTuner,want_all_kb


/*
   Most of this file is based off the Key Tuner, so most comments are stripped. Instead of using local strings and double arrays, it uses global gmem.
   Due to the fact that JS does not support global strings right now, the scales_str (slot 2) and scale_list were converted to gmem float doubles.
   They are both packed for each scale into one element: 30-bit element. Note that the reason only 30-bits are packed per double, is because to extract
   the lower data FAST, we need to convert to integer. JS converts the doubles to 32-bit integers when doing this, and thus we can't use values above
   31 bits (due to sign too), which makes packing bigger values in the double impractical (without floor which is very slow), because a mask like
   "float & $~8" will fail to extract the bottom 8 bits if the packed value is too large in total. The only way is with floor() but that's too messy...

   The layout of the KeyTuner gmem is like this:

    gmem[0]: The number of Scales.
    gmem[1]...gmem[n]: An array for scales_str (string slot 2 in original) and scale_list (offsets), 1 element for each scale; each is composed of 30-bits
                       7 bits for Key, 8 bits for Number of Notes in the Scale, and 15 bits for the absolute Offset to the start of the scale (fast lookup).

    Then comes the actual array data, which is the same as Key Tuner (which starts at 176 in that one) but this one depends on the number of scales.

    (technically, we can get the number of notes for each scale from the offsets differences, but I store both because whatever... much simpler code meh, screw it)
    (for maximum packing, we could store up to 52 bits in the double via floor, and no redundant info... but with 128 scales it's only 1 KB so it doesn't matter...)
*/


@init
strcpy(0, "");  loop(64, str_setchar(0, -0.25, $x10001000, 'iu'));
notebend = 16;
lastbend = bend = 8192;



// put these in @slider, because @init gets imported in Slave regardless of it existing already
@slider
frac_to_bend===0 ? (
  frac_to_bend = 4096;
  cnt_max = 16;

  key = -1;
  key_tuning = -528;
  scale_notes = 1;
  iscale_notes= 1;
  scale_repeat = 12;
  midi_root = key;  scale_root = key_tuning;

  scale = 3;

  gmem[1] = $x100B0;  // key=48, 1 note, offset=2
  gmem[2] = 48;
  gmem[3] = scale_repeat;
  gmem[0] = 1;
);
cnt_last = 32;  loop(cnt_max-1, cnt_last[0] = cnt_last+=1);  cnt_last[0] = 32;  cnt = 32;



@serialize
file_avail(0) < 0 ? (
  strcpy(2, "");  str_setchar(2, 0, 8192/frac_to_bend + 0.25 + (cnt_max-1)*256 + vst_detune_mode, 'su');
  i = 1; b = gmem[0];
  loop(b, n = gmem[i];  str_setchar(2, -0.25, n*2 - (n & $~7), 'su');  i += 1);
  file_string(0, 2);

  i = b+1; n = 1;
  loop(b,
   loop((gmem[n]>>7 & $~8) +1,  a = gmem[i]; file_var(0, a); i += 1);
   n += 1;
  );
) : (
  gmem[0] = 0;
  file_string(0, 2);
  i = 1; b = strlen(2)/2-1;  a = scale = b+1;
  loop(b,
   n = str_getchar(2, i*2, 'su');  gmem[i] = (n+(n & $~7))/2 + a*32768;  i += 1;
   loop(n>>8 + 1,  file_var(0, n); gmem[a] = n; a += 1);
  );

  cnt_max = (vst_detune_mode = str_getchar(2, 0, 'su')) >> 8;
  frac_to_bend = (vst_detune_mode -= cnt_max*256) & $~7;  vst_detune_mode -= frac_to_bend;
  frac_to_bend = frac_to_bend ? 8192/frac_to_bend : 16384;  cnt_max += 1;

  scale_notes = gmem[1]/128 & $~8;  iscale_notes = 1/scale_notes;
  scale_repeat = gmem[scale + scale_notes];

  key = gmem[1] & $~7;  key_tuning = (key*iscale_notes + 1)|0;
  key -= key_tuning*scale_notes;
  key_tuning = gmem[scale] - key_tuning*scale_repeat;
  midi_root = key;  scale_root = key_tuning;

  gmem[0] = b;
);
strcpy(2, "");



@block
midirecv(o,a,b,c) ? (
 lastoffset = o;
 while(
  a > $x9F ? (
   n = a & $xF0;
   n===$xE0 ? (
    bend = b + c*128;
   ) : (
    a===$xAF ? (
     b[48] += c+(c>63)-64;
    ) : (
     n!==$xC0 ? (
      midisend(o, a, b, c);
     ) : (
      (a = gmem[0]) ? (
       key = gmem[min(b+1, a)];  scale = key>>15;
       scale_notes = key>>7 - scale*256;  key &= $~7;
       iscale_notes = 1/scale_notes;
       scale_repeat = gmem[scale + scale_notes];

       key_tuning = (key*iscale_notes + 1)|0;
       key -= key_tuning*scale_notes;
       key_tuning = gmem[scale] - key_tuning*scale_repeat;

       midi_root = key;  scale_root = key_tuning;
      );
     );
    );
   );
  ) : (
   a < $x90 ? (
    a!==$x8F ? (
     b *= 2;
     (a = str_getchar(0, b, 'su')) < $x1000 ? (
      str_setchar(0, b, $x1000, 'su');
      b=a>>8;  (a &= $~8)<128 ? midisend(o, b + $x80, a, c);

      b += 32;
      cnt_last > 0 ? (
       b!==cnt && b!==cnt_last ? (
        c = cnt_last[0];
        b!==c ? (
         a = c;  while((o = c[0]) !== b) (c = o);

         (o = b[0]) !== c ? c[0] = o;
         cnt_last[0] = b;
         b[0] = a;
        );
        cnt_last = b;
       );
      ) : (
       b!==cnt ? (
        a = cnt_max-2;
        c = cnt;  while((o = c[0]) !== b) (c = o; a -= 1);

        c[0] = b[0];
        b[0] = cnt;

        loop(a, c = c[0]);
        c[0] = b;
        cnt = b;
       );
       cnt_last = b;
      );
     );
    );

   ) : (
    a!==$x9F ? (str_setchar(1, -0.25, b + c*256, 'su'))
    : (
     midi_root = b-key;  midi_root -= (midi_root*iscale_notes - 0.00390625 |0)*scale_notes;

     scale_root = key_tuning + gmem[scale + midi_root];
     (midi_root += key) >= 0 ? (
       scale_root -= scale_repeat;
       midi_root -= scale_notes;
     );
    );
   );
  );

  (m = midirecv(o,a,b,c))===0  ||  lastoffset < o ? (
   i = -2;
   loop(strlen(1)/2,
    v = str_getchar(1, (i += 2), 'su');
    n = v & $~8;  v -= n;

    x = (y = n-midi_root)*iscale_notes - 0.00390625 |0;
    y = scale_root + x*scale_repeat + gmem[scale + y - x*scale_notes];
    (t=n[48]) ? (y += (rand(1)+t-0.5)/128; n[48] = 0);

    ch = cnt-32;  n *= 2;
    (x = str_getchar(0, n, 'su')) < $x1000 ? (
     ch = x>>8;
     midisend(lastoffset, ch + $x80, x - ch*256);
    );

    x = (y+0.5)|0;
    abs(x-63.5) < 64 ?
    (
     y -= x;
     vst_detune_mode ? (
      t = (y*100+50.5 |0);
      y -= (t - 50)/100;
      midisend(lastoffset, ch + $xB0, 119, t);
     );
     y *= frac_to_bend;
     notebend[ch] = y;

     str_setchar(0, n, x + ch*256, 'su');

     (y += bend)  !==  ch[0] ? (
       ch[0] = y;  y = min(max(y, 0), 16383);
       midisend(lastoffset, ch + $xE0, y + (y & $x3F80));
     );

     midisend(lastoffset, ch + $x90, x+v);
    ) : (
     str_setchar(0, n, ch*256 + 128, 'su');
    );
    ch===cnt-32 ? (
     cnt===cnt_last ? cnt_last = 0;
     cnt = cnt[0];
    );
   );
   strcpy(1, "");

   bend !== lastbend ? (
    lastbend = bend;  i = 0;

    loop(cnt_max,
     (n = notebend[0]+bend) !== i[0] ? (
      i[0] = n;  n = min(max(n, 0), 16383);
      midisend(lastoffset, i + $xE0, n + (n & $x3F80));
     );
     notebend += 1;  i += 1;
    );
    notebend = 16;
   );

   lastoffset = o;
  );

  m
 );
);




@gfx
(mouse_cap &= 12) ? (
 gb = (8192/frac_to_bend + 0.25) |0;
 (mouse_cap > 4 ? gb : cnt_max) += mouse_wheel/120;
 while((ga = gfx_getchar()) >= 1) (
  ga === 'up' ? (mouse_cap > 4 ? gb += 1 : cnt_max += 1);
  ga === 'down' ? (mouse_cap > 4 ? gb -= 1 : cnt_max -= 1);
  ga === 'pgdn' ? (mouse_cap > 4 ? gb -= 10 : cnt_max -= 4);
  ga === 'pgup' ? (mouse_cap > 4 ? gb += 10 : cnt_max += 4);
  ga === ' ' ? vst_detune_mode ~= 128;
 );
 gb = min(max(gb, 0), 127);  gb===0 ? gb = 0.5;
 frac_to_bend = 8192/gb;  cnt_max = min(max(cnt_max, 1), 16);
 cnt_last = 32;  loop(cnt_max-1, cnt_last[0] = cnt_last+=1);  cnt_last[0] = 32;  cnt = 32;
) : (
 gpos -= mouse_wheel*0.0291666666666666667;
 while((ga = gfx_getchar()) >= 1) (
  ga === 'up' ? gpos -= 0.5;
  ga === 'down' ? gpos += 0.5;
  ga === 'pgdn' ? gpos += 20;
  ga === 'pgup' ? gpos -= 20;
  ga === ' ' ? gmode ~= 1;
 );
 ga = gmem[0];  gb = gmem[ga];
 gpos = min(max(gpos, 0), (gb>>7) - (gb>>15)*255 + ga*4 - 1);
);
gfx_clear = $~24;
gfx_r = gfx_g = gfx_b = 0;
gfx_y = gfx_texth*(1.75 - gpos);

mouse_cap = 1;
while(gfx_y <= gfx_h  &&  mouse_cap <= gmem[0])
(
  mouse_y = ((ga = (gb = gmem[mouse_cap])>>7 & $~8) + 6)*gfx_texth;
  gfx_y > -mouse_y ?
  (
   mouse_wheel = gmem[mouse_cap]>>15;
   gfx_x = 0;  mouse_x = #;  sprintf(mouse_x, "%.8f", gmode ? gmem[mouse_wheel] : exp(gmem[mouse_wheel]*0.05776226504666210912)*8.1757989156437073337);  mouse_y = -1;
   while (str_getchar(mouse_x, mouse_y)===$'0') (mouse_y -= 1);
   strncpy(mouse_x, mouse_x, strlen(mouse_x)+mouse_y+(str_getchar(mouse_x, mouse_y)!==$'.'));
   gmode===0 ? strcat(mouse_x, "Hz");

   gfx_y += gfx_texth;
   gfx_y >=0 ? (gfx_x = 0;  gfx_setfont(1, "Arial", 16, 'b');  gfx_printf("    [Scale %d]", mouse_cap));  gfx_setfont(1, "Arial", 16);  gfx_y += gfx_texth;
   gfx_y < 0 ? (gfx_y += gfx_texth) : (gfx_x = 0;  gfx_printf("    Key: %d\n    Key Tuning: %s", gb&$~7, mouse_x));  gfx_y += gfx_texth;
   gfx_y < 0 ? (gfx_y += gfx_texth) : (gfx_x = 0;  gfx_printf("    Number of Notes: %d\n    Notes:", ga));  gfx_y += gfx_texth;

   loop(ga,
    mouse_wheel += 1;
    gfx_y >= 0 ? (
     gfx_x = 0;  ga = #;  sprintf(ga, "%.8f", gmem[mouse_wheel] * 100);  gb = -1;
     while (str_getchar(ga, gb)===$'0') (gb -= 1);
     strncpy(ga, ga, strlen(ga)+gb+(str_getchar(ga, gb)!==$'.'));
     gfx_printf("        %s", ga);
    );
    gfx_y += gfx_texth;
   );
  ) : (
   gfx_y += mouse_y;
  );
  mouse_cap += 1;
);

gfx_x = 0;  gfx_y = gfx_texth*0.25;  gfx_r = gfx_g = gfx_b = 1;  gfx_rect(0, 0, gfx_w, gfx_texth*1.75);
gfx_r = gfx_g = gfx_b = 0;  gfx_setfont(1, "Arial", 16, 'b');

gfx_drawstr("    Pitchbend Range: ");  frac_to_bend===16384 ? gfx_drawstr("1/2") : gfx_printf("%d", 8192/frac_to_bend + 0.5);  gfx_printf("        [ %d Channels ]", cnt_max);
vst_detune_mode ? gfx_drawstr("        ( VST Detune Mode )");
gfx_y += gfx_texth*1.5;  gfx_line(0, gfx_y, gfx_w, gfx_y, 0);

mouse_wheel = 0;